Skip to content

Harden URL reads and remote device call targeting - #619

Open
AMeloDragon wants to merge 3 commits into
wonderwhy-er:mainfrom
AMeloDragon:amelodragon-security-audit-critical-fixes
Open

Harden URL reads and remote device call targeting#619
AMeloDragon wants to merge 3 commits into
wonderwhy-er:mainfrom
AMeloDragon:amelodragon-security-audit-critical-fixes

Conversation

@AMeloDragon

@AMeloDragon AMeloDragon commented Aug 1, 2026

Copy link
Copy Markdown

Why

This change closes two high-risk security gaps found during audit: server-side request forgery via URL-based file reads, and cross-device remote tool execution when calls are not explicitly bound to a target device.

What changed

  • Added URL security validation in readFileFromUrl to reduce SSRF risk:
    • HTTPS-only enforcement
    • Hostname/IP checks that block localhost, loopback, link-local, and private address ranges
    • DNS resolution checks to prevent hostname-to-private-IP bypass
    • Manual redirect handling with per-hop re-validation and a redirect limit
  • Tightened remote device authorization checks so calls are processed only when device_id is present and exactly matches the current device:
    • Filter at realtime subscription callback (remote-channel.ts)
    • Enforce again at execution point (device.ts)

Notes for reviewers

  • The URL hardening intentionally rejects previously accepted non-HTTPS and internal-network targets.
  • Dependency audit still reports vulnerable packages in the wider tree; this PR focuses on direct code-level exploit paths and does not include dependency upgrade churn.

Summary by CodeRabbit

  • Bug Fixes

    • Improved device targeting so only calls and realtime events intended for the active device are processed.
    • Strengthened URL fetching security by blocking unsafe addresses, validating redirects, and enforcing HTTPS.
    • Improved timeout cleanup and maintained clearer error handling for failed downloads.
  • New Features

    • Added support for converting downloaded PDF data into Markdown from multiple standard data formats.
    • Improved PDF handling for securely downloaded files.

Block SSRF-prone URL targets in read_file URL mode and require explicit device_id matching before processing remote calls.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8688cc4e-f76d-46dc-bba1-d3e54c1d535b

📥 Commits

Reviewing files that changed from the base of the PR and between e4b8e80 and 36ab323.

📒 Files selected for processing (1)
  • src/tools/filesystem.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/tools/filesystem.ts

📝 Walkthrough

Walkthrough

The change filters remote tool calls by device ID and adds SSRF protections to URL fetching. URL validation checks HTTPS targets and resolved addresses, validates redirects, limits redirect hops, and parses PDFs from buffered responses.

Changes

Remote tool-call routing

Layer / File(s) Summary
Device-targeted event filtering
src/remote-device/device.ts, src/remote-device/remote-channel.ts
Tool calls and realtime events without a matching device ID are logged and ignored. Matching events continue to the tool-call handler.

Secure URL fetching

Layer / File(s) Summary
Validated URL fetch flow
src/tools/filesystem.ts
HTTPS URLs are resolved and checked against restricted address ranges. Requests pin validated addresses, redirects are followed manually with a five-hop limit, and responses are buffered before PDF parsing. Timeout cleanup runs in finally.
Buffer-based PDF parsing
src/tools/pdf/markdown.ts, src/tools/pdf/index.ts
parsePdfToMarkdown delegates to the exported parsePdfBufferToMarkdown helper. The helper accepts Buffer, ArrayBuffer, and Uint8Array inputs.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the two main changes: hardened URL reads and stricter remote device call targeting.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/tools/filesystem.ts (1)

48-87: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Extend the blocked IP ranges.

isPrivateIpAddress misses several ranges that are commonly used for SSRF against cloud and local services:

  • IPv4: 192.0.0.0/24, 198.18.0.0/15, multicast 224.0.0.0/4, reserved 240.0.0.0/4, and broadcast 255.255.255.255.
  • IPv6: the unspecified address ::, multicast ff00::/8, NAT64 64:ff9b::/96, and IPv4-compatible ::a.b.c.d forms.

Note that 169.254.169.254 is already blocked by the a === 169 && b === 254 branch, so cloud metadata over IPv4 is covered.

🔒 Proposed additional checks
         const [a, b] = octets;
         return (
             a === 0 || // "this network"
             a === 10 ||
             a === 127 ||
             (a === 100 && b >= 64 && b <= 127) || // carrier-grade NAT
             (a === 169 && b === 254) ||
             (a === 172 && b >= 16 && b <= 31) ||
-            (a === 192 && b === 168)
+            (a === 192 && b === 168) ||
+            (a === 192 && b === 0) || // 192.0.0.0/24 IETF protocol assignments
+            (a === 198 && (b === 18 || b === 19)) || // benchmarking
+            a >= 224 || // multicast, reserved, broadcast
+            ip === '255.255.255.255'
         );
     }
 
     // IPv6 local/loopback/IPv4-mapped ranges
     if (ipVersion === 6) {
-        if (ip === '::1') {
+        if (ip === '::1' || ip === '::') {
             return true;
         }
+        if (ip.startsWith('ff')) {
+            return true; // multicast (ff00::/8)
+        }
+        if (ip.startsWith('64:ff9b:')) {
+            return true; // NAT64
+        }
         if (ip.startsWith('fc') || ip.startsWith('fd')) {
             return true; // unique local address space (fc00::/7)
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/filesystem.ts` around lines 48 - 87, Extend isPrivateIpAddress to
classify the requested IPv4 ranges as private, including 192.0.0.0/24,
198.18.0.0/15, multicast 224.0.0.0/4, reserved 240.0.0.0/4, and 255.255.255.255.
In its IPv6 handling, also block ::, ff00::/8, NAT64 addresses under
64:ff9b::/96, and IPv4-compatible ::a.b.c.d forms by delegating the embedded
IPv4 value to isPrivateIpAddress; preserve the existing IPv4-mapped handling and
metadata-range coverage.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/remote-device/remote-channel.ts`:
- Around line 217-222: Update the Realtime subscription and its underlying
RLS/routing configuration to enforce a device-level constraint equivalent to
new.device_id = auth.uid() before remote call payloads reach the handler. Keep
the payloadDeviceId check in the remote-channel handler as defense in depth, but
do not rely on handleNewToolCall or local filtering as the primary authorization
boundary.

In `@src/tools/filesystem.ts`:
- Around line 508-513: Update the PDF handling around isPdf and
parsePdfToMarkdown to parse the already fetched, validated response body via a
buffer-accepting entry point in the PDF markdown parser, avoiding any second URL
request and preserving the existing redirect validation, abort signal, and
timeout protections. Change PDF extension detection to use currentUrl.pathname
so query strings and fragments do not prevent recognizing .pdf resources.
- Around line 471-499: Update the redirect-fetch loop around
validateRemoteReadUrl so each fetch connection is constrained to the already
validated address for its hostname, preventing a second unconstrained DNS
resolution; preserve the original hostname for Host/TLS SNI when connecting by
IP. Before following each redirect, consume or cancel the prior redirect
response body to release resources.

---

Nitpick comments:
In `@src/tools/filesystem.ts`:
- Around line 48-87: Extend isPrivateIpAddress to classify the requested IPv4
ranges as private, including 192.0.0.0/24, 198.18.0.0/15, multicast 224.0.0.0/4,
reserved 240.0.0.0/4, and 255.255.255.255. In its IPv6 handling, also block ::,
ff00::/8, NAT64 addresses under 64:ff9b::/96, and IPv4-compatible ::a.b.c.d
forms by delegating the embedded IPv4 value to isPrivateIpAddress; preserve the
existing IPv4-mapped handling and metadata-range coverage.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 979e45f1-8645-4fe5-9d4b-1797c8f3f33c

📥 Commits

Reviewing files that changed from the base of the PR and between 1eccc8b and b87faf4.

📒 Files selected for processing (3)
  • src/remote-device/device.ts
  • src/remote-device/remote-channel.ts
  • src/tools/filesystem.ts

Comment thread src/remote-device/remote-channel.ts
Comment thread src/tools/filesystem.ts Outdated
Comment thread src/tools/filesystem.ts Outdated
Pin URL fetch DNS to validated addresses, cancel redirect bodies, and parse PDF bytes from the validated fetch response to avoid a second unvalidated request.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 1, 2026 15:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens two security-sensitive areas of the codebase: URL-based remote reads (to reduce SSRF exposure) and remote tool-call processing (to ensure calls are only executed by the explicitly targeted device). It also refactors PDF parsing so URL reads can reuse already-fetched PDF bytes instead of re-downloading.

Changes:

  • Added HTTPS-only URL validation with DNS/IP allowlisting and manual redirect re-validation for readFileFromUrl.
  • Enforced strict device_id matching for remote tool calls both at realtime subscription handling and at execution time.
  • Introduced parsePdfBufferToMarkdown to parse already-fetched PDF bytes (and updated exports/imports accordingly).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/tools/pdf/markdown.ts Adds a buffer-based PDF-to-Markdown entry point and routes string-source parsing through it.
src/tools/pdf/index.ts Re-exports the new parsePdfBufferToMarkdown API.
src/tools/filesystem.ts Implements URL SSRF hardening, redirect handling, and switches PDF URL handling to parse from fetched bytes.
src/remote-device/remote-channel.ts Drops realtime tool-call events that are not explicitly targeted to the current device.
src/remote-device/device.ts Requires device_id to be present and match the current device before executing a tool call.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/tools/filesystem.ts
Comment thread src/tools/filesystem.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/tools/pdf/markdown.ts (1)

262-283: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not leave parsePdfToMarkdown accepting URL strings.

parsePdfToMarkdown() still calls fetch(source) for source values that start with http:// or https://, without HTTPS pinning, redirect/timeout limits, or private/loopback blocking. Accept only local PDF paths here, or route URL fetches through the same validated URL read path used by readFileFromUrl() before calling parsePdfBufferToMarkdown().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/pdf/markdown.ts` around lines 262 - 283, The parsePdfToMarkdown
flow must not directly fetch URL sources through loadPdfToBuffer. Restrict
loadPdfToBuffer and parsePdfToMarkdown to local PDF paths, or reuse the existing
validated readFileFromUrl path for URLs—including its HTTPS, redirect, timeout,
and private/loopback protections—before passing data to
parsePdfBufferToMarkdown.
src/tools/filesystem.ts (1)

607-618: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Drop the instanceof DOMException check for fetch timeouts.

cross-fetch 4.1.0 uses node-fetch 2.7.0 in Node environments, and that AbortError is an Error subclass, not a DOMException. Use error.name === 'AbortError' so these fetch timeouts get the specific timeout message instead of Failed to fetch URL.

💡 Proposed fix
-        const errorMessage = error instanceof DOMException && error.name === 'AbortError'
+        const errorMessage = error instanceof Error && error.name === 'AbortError'
             ? `URL fetch timed out after ${FILE_OPERATION_TIMEOUTS.URL_FETCH}ms: ${url}`
             : `Failed to fetch URL: ${error instanceof Error ? error.message : String(error)}`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/filesystem.ts` around lines 607 - 618, Update the error
classification in the URL fetch catch block to identify abort timeouts using
error.name === 'AbortError' without requiring error to be a DOMException.
Preserve the existing timeout message for abort errors and the generic failure
message for all other errors in the fetch flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/tools/filesystem.ts`:
- Around line 607-618: Update the error classification in the URL fetch catch
block to identify abort timeouts using error.name === 'AbortError' without
requiring error to be a DOMException. Preserve the existing timeout message for
abort errors and the generic failure message for all other errors in the fetch
flow.

In `@src/tools/pdf/markdown.ts`:
- Around line 262-283: The parsePdfToMarkdown flow must not directly fetch URL
sources through loadPdfToBuffer. Restrict loadPdfToBuffer and parsePdfToMarkdown
to local PDF paths, or reuse the existing validated readFileFromUrl path for
URLs—including its HTTPS, redirect, timeout, and private/loopback
protections—before passing data to parsePdfBufferToMarkdown.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 79f8416d-d973-477d-84ba-2933af4c2555

📥 Commits

Reviewing files that changed from the base of the PR and between b87faf4 and e4b8e80.

📒 Files selected for processing (3)
  • src/tools/filesystem.ts
  • src/tools/pdf/index.ts
  • src/tools/pdf/markdown.ts

Handle expanded IPv6 loopback forms in SSRF checks and avoid an extra PDF buffer copy during validated URL reads.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants